#include "dwarf/internal.hpp"
using namespace std;

namespace dwarf
{

struct dwarf::impl {
    impl(const std::shared_ptr<loader> &l)
        : l(l), have_type_units(false) {}

    std::shared_ptr<loader> l;

    std::shared_ptr<section> sec_info;
    std::shared_ptr<section> sec_abbrev;

    std::vector<compilation_unit> compilation_units;

    std::unordered_map<uint64_t, type_unit> type_units;
    bool have_type_units;

    std::map<section_type, std::shared_ptr<section>> sections;
};

dwarf::dwarf(const std::shared_ptr<loader> &l)
    : m(make_shared<impl>(l))
{
    const void *data;
    size_t size;

    // 从loader对象中加载名为info的节的数据
    data = l->load(section_type::info, &size);
    if (!data)
        throw format_error("required .debug_info section missing");
    m->sec_info = make_shared<section>(section_type::info, data, size, byte_order::lsb);

    // 解析数据
    cursor endcur(m->sec_info);
    // 读取长度
    section_length length = endcur.fixed<uword>();
    if (length == 0xffffffff)
        endcur.fixed<uint64_t>();
    // 如果版本字段的值小于 0xFFFF, 则为小端序
    uhalf version = endcur.fixed<uhalf>();
    uhalf versionbe = (version >> 8) | ((version & 0xFF) << 8);
    // 如果大端序的版本字段小于小端序的版本字段，则表示字节序是大端序
    if (versionbe < version) {
        m->sec_info = make_shared<section>(section_type::info, data, size, byte_order::msb);
    }
    // 读取 .debug_abbrev section 
    data = l->load(section_type::abbrev, &size);
    if (!data)
        throw format_error("required .debug_abbrev section missing");
    m->sec_abbrev = make_shared<section>(section_type::abbrev, data, size, m->sec_info->ord);
    // 用来遍历`m->sec_info`节中的编译单元
    cursor infocur(m->sec_info);
    while (!infocur.end()) {
        m->compilation_units.emplace_back(
            *this, infocur.get_section_offset());
        // 定位到下一个编译单元的开头
        infocur.subsection();
    }
}

const std::vector<compilation_unit> &
dwarf::compilation_units() const
{
    static std::vector<compilation_unit> empty;
    if (!m)
        return empty;
    return m->compilation_units;
}

const type_unit & dwarf::get_type_unit(uint64_t type_signature) const
{
    if (!m->have_type_units) {
        cursor tucur(get_section(section_type::types));
        while (!tucur.end()) {
            type_unit tu(*this, tucur.get_section_offset());
            m->type_units[tu.get_type_signature()] = tu;
            tucur.subsection();
        }
        m->have_type_units = true;
    }
    if (!m->type_units.count(type_signature))
        throw out_of_range("type signature 0x" + to_hex(type_signature));
    return m->type_units[type_signature];
}

std::shared_ptr<section> dwarf::get_section(section_type type) const
{
    if (type == section_type::info)
        return m->sec_info;
    if (type == section_type::abbrev)
        return m->sec_abbrev;

    auto it = m->sections.find(type);
    if (it != m->sections.end())
        return it->second;

    size_t size;
    const void *data = m->l->load(type, &size);
    if (!data)
        throw format_error(std::string(elf::section_type_to_name(type)) + " section missing");
    m->sections[type] = std::make_shared<section>(section_type::str, data, size, m->sec_info->ord);
    return m->sections[type];
}


struct unit::impl {
    const dwarf file;
    const section_offset offset;
    const std::shared_ptr<section> subsec;
    const section_offset debug_abbrev_offset;
    const section_offset root_offset;

    const uint64_t type_signature;
    const section_offset type_offset;

    die root, type;
    line_table lt;

    bool have_abbrevs;
    std::vector<abbrev_entry> abbrevs_vec;
    std::unordered_map<abbrev_code, abbrev_entry> abbrevs_map;

    impl(const dwarf &file, section_offset offset,
         const std::shared_ptr<section> &subsec,
         section_offset debug_abbrev_offset, section_offset root_offset,
         uint64_t type_signature = 0, section_offset type_offset = 0)
        : file(file), offset(offset), subsec(subsec),
          debug_abbrev_offset(debug_abbrev_offset),
          root_offset(root_offset), type_signature(type_signature),
          type_offset(type_offset), have_abbrevs(false) {}

    void force_abbrevs();
};

unit::~unit()
{
}

const dwarf & unit::get_dwarf() const
{
    return m->file;
}

section_offset
unit::get_section_offset() const
{
    return m->offset;
}

const die & unit::root() const
{
    if (!m->root.valid()) {
        m->force_abbrevs();
        m->root = die(this);
        m->root.read(m->root_offset);
    }
    return m->root;
}

const std::shared_ptr<section> & unit::data() const
{
    return m->subsec;
}

const abbrev_entry & unit::get_abbrev(abbrev_code acode) const
{
    if (!m->have_abbrevs)
        m->force_abbrevs();

    if (!m->abbrevs_vec.empty()) {
        if (acode >= m->abbrevs_vec.size())
            goto unknown;
        const abbrev_entry &entry = m->abbrevs_vec[acode];
        if (entry.code == 0)
            goto unknown;
        return entry;
    } else {
        auto it = m->abbrevs_map.find(acode);
        if (it == m->abbrevs_map.end())
            goto unknown;
        return it->second;
    }

unknown:
    throw format_error("unknown abbrev code 0x" + to_hex(acode));
}

void unit::impl::force_abbrevs()
{
    if (have_abbrevs)
        return;

    cursor c(file.get_section(section_type::abbrev),
             debug_abbrev_offset);
    abbrev_entry entry;
    abbrev_code highest = 0;
    while (entry.read(&c)) {
        abbrevs_map[entry.code] = entry;
        if (entry.code > highest)
            highest = entry.code;
    }

    if (highest * 10 < abbrevs_map.size() * 15) {
        abbrevs_vec.resize(highest + 1);
        for (auto &entry : abbrevs_map)
            abbrevs_vec[entry.first] = move(entry.second);
        abbrevs_map.clear();
    }

    have_abbrevs = true;
}

compilation_unit::compilation_unit(const dwarf &file, section_offset offset)
{

    cursor cur(file.get_section(section_type::info), offset);
    std::shared_ptr<section> subsec = cur.subsection();
    cursor sub(subsec);
    sub.skip_initial_length();
    uhalf version = sub.fixed<uhalf>();
    if (version < 2 || version > 4)
        throw format_error("unknown compilation unit version " + std::to_string(version));
    section_offset debug_abbrev_offset = sub.offset();
    ubyte address_size = sub.fixed<ubyte>();
    subsec->addr_size = address_size;

    m = make_shared<impl>(file, offset, subsec, debug_abbrev_offset,
                          sub.get_section_offset());
}

const line_table &
compilation_unit::get_line_table() const
{
    if (!m->lt.valid()) {
        const die &d = root();
        if (!d.has(DW_AT::stmt_list) || !d.has(DW_AT::name))
            goto done;

        shared_ptr<section> sec;
        try {
            sec = m->file.get_section(section_type::line);
        } catch (format_error &e) {
            goto done;
        }

        auto comp_dir = d.has(DW_AT::comp_dir) ? at_comp_dir(d) : "";

        m->lt = line_table(sec, d[DW_AT::stmt_list].as_sec_offset(),
                           m->subsec->addr_size, comp_dir,
                           at_name(d));
    }
done:
    return m->lt;
}


type_unit::type_unit(const dwarf &file, section_offset offset)
{
    cursor cur(file.get_section(section_type::types), offset);
    std::shared_ptr<section> subsec = cur.subsection();
    cursor sub(subsec);
    sub.skip_initial_length();
    uhalf version = sub.fixed<uhalf>();
    if (version != 4)
        throw format_error("unknown type unit version " + std::to_string(version));
    section_offset debug_abbrev_offset = sub.offset();
    ubyte address_size = sub.fixed<ubyte>();
    subsec->addr_size = address_size;
    uint64_t type_signature = sub.fixed<uint64_t>();
    section_offset type_offset = sub.offset();

    m = make_shared<impl>(file, offset, subsec, debug_abbrev_offset,
                          sub.get_section_offset(), type_signature,
                          type_offset);
}

uint64_t
type_unit::get_type_signature() const
{
    return m->type_signature;
}

const die &
type_unit::type() const
{
    if (!m->type.valid()) {
        m->force_abbrevs();
        m->type = die(this);
        m->type.read(m->type_offset);
    }
    return m->type;
}

} // namespace dwarf
